//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Workflow.ComponentModel;
using System.Collections;
using System.Collections.Specialized;
using System.Workflow.ComponentModel.Design;
using System.ComponentModel.Design;
using System.Workflow.ComponentModel.Compiler;
using System.Data.Odbc;
using System.Xml;
using Microsoft.Research.ScientificWorkflow;
using System.IO;
using Microsoft.Research.ScientificWorkflow.TridentUtilities;
using WorkflowData;

namespace Microsoft.Research.ScientificWorkflow.Activities
{
    [Name("Hyper Cube DB Writer")]
    [Description("Writes the hyper cube to a DB")]
    [WorkflowCategory("Database")]
    class HyperCubeDBWriter: Activity
    {
        #region Private Member Data

        private OdbcConnection DbConnection;
        private OdbcCommand Command;
        
        #endregion

        #region Const Strings

        private const string dbConfigXML = "DBWriterConfig.xml";
        private const string xDBNodeFetch = "/Databases/Database[@Type=\"{0}\"]";
        private const string xConnNodeFetch = "/Databases/Database/ConnectionString";
        private const string xCreateDBFetch = "/Databases/Database/Queries/Query[@Name=\"CreateDB\"]";
        private const string xCreateDimTable = "/Databases/Database/Tables/Table[@Name=\"DimensionTable\"]";
        private const string xVarTypeFetch = "/Databases/Database/Types/Type[@NcType=\"{0}\"]";
        private const string xAttCreate = "/Databases/Database/Tables/Table[@Name=\"AttributeTable\"]";
        private const string xDimensionMapCreate = "/Databases/Database/Tables/Table[@Name=\"DimensionalityTable\"]";
        private const string xCreateDataTable = "/Databases/Database/Tables/Table[@Name=\"HyperCubeDataTable\"]";
        
        private const string xInsDimValues = "/Databases/Database/Queries/Query[@Name=\"InsertDimValues\"]";
        private const string xInsDimensionality = "/Databases/Database/Queries/Query[@Name=\"InsertDimensionality\"]";
        private const string xInsDataValues = "/Databases/Database/Queries/Query[@Name=\"InsertDataValues\"]";
        private const string xInsAttribValues = "/Databases/Database/Queries/Query[@Name=\"InsertAttribValues\"]";

        private const string xCreateDataSP = "/Databases/Database/Procedures/Procedure[@Name=\"InsertHyperCubeData\"]";

        private const string xIndex = "@xInexVal";
        private const string yIndex = "@yInexVal";
        private const string zIndex = "@zInexVal";
        private const string tIndex = "@tInexVal";
        #endregion

        #region Dependency Properties

        public static DependencyProperty DBTypeProperty = DependencyProperty.Register("DBType",
            typeof(string), typeof(HyperCubeDBWriter));

        [RequiredInputParam]
        [Name("DB Type")]
        [Description("String value specifying the DB type to which Cube would be persisted.")]
        public string DBType
        {
            get { return ((string)(base.GetValue(HyperCubeDBWriter.DBTypeProperty))); }
            set { base.SetValue(HyperCubeDBWriter.DBTypeProperty, value); }
        }

        public static DependencyProperty DBServerProperty = DependencyProperty.Register("DBServer",
            typeof(string), typeof(HyperCubeDBWriter));

        [RequiredInputParam]
        [Name("DB Server")]
        [Description("Name of the server on which the DB is present.")]
        public string DBServer
        {
            get { return ((string)(base.GetValue(HyperCubeDBWriter.DBServerProperty))); }
            set { base.SetValue(HyperCubeDBWriter.DBServerProperty, value); }
        }

        public static DependencyProperty DBNameProperty = DependencyProperty.Register("DBName",
            typeof(string), typeof(HyperCubeDBWriter));

        [RequiredInputParam]
        [Name("DB Name")]
        [Description("Name of the database to be used for persisting.")]
        public string DBName
        {
            get { return ((string)(base.GetValue(HyperCubeDBWriter.DBNameProperty))); }
            set { base.SetValue(HyperCubeDBWriter.DBNameProperty, value); }
        }

        public static DependencyProperty InputCubeProperty =
            DependencyProperty.Register("InputCube", typeof(HyperCube), typeof(HyperCubeDBWriter));

        [RequiredInputParam]
        [Name("Input HyperCube")]
        [Description("Hyper Cube with data to be written to the DB.")]
        public HyperCube InputCube
        {
            get { return ((HyperCube)(base.GetValue(HyperCubeDBWriter.InputCubeProperty))); }
            set { base.SetValue(HyperCubeDBWriter.InputCubeProperty, value); }
        }

        #endregion

        #region Over Ridden Methods

        protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
        {
            XmlNode dbNode = this.LoadXml();

            this.CreatConnection(dbNode);

            this.CreateDimensionTables(dbNode);

            this.CreateAttributeValuesTable(dbNode);

            this.CreateDimensionalityTable(dbNode);

            this.CreateDataTable(dbNode);

            this.CreateDataSP(dbNode);

            this.InsertDimensionValues(dbNode);

            this.InsertDimensionalityValues(dbNode);

            this.InsertAttributeValues(dbNode);
            
            this.InsertHyperCubeData(dbNode);
            
            this.Clean();

            return ActivityExecutionStatus.Closed;
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// This method closes the open connections to the DB. 
        /// </summary>
        private void Clean()
        {
            this.Command.Dispose();
            this.DbConnection.Close();
            this.DbConnection.Dispose();
        }

        /// <summary>
        /// This method executes the command on the DB as specified
        /// by the input command string.
        /// </summary>
        /// <param name="cmdString"></param>
        private void ExecuteCommand(string cmdString)
        {
            try
            {
                Command.CommandText = cmdString;
                Command.ExecuteNonQuery();
            }
            catch (Exception)
            {
            }
        }

        /// <summary>
        /// This method returns the odbc type for the corresponding C#
        /// type
        /// </summary>
        private OdbcType GetODBCType(Type typ)
        {
            string clrType = typ.ToString();
            switch (clrType)
            {
                case "System.Byte":
                    return OdbcType.SmallInt;
                case "System.Int16":
                    return OdbcType.SmallInt;
                case "System.Int32":
                    return OdbcType.Int;
                case "System.Single":
                    return OdbcType.Double;
                case "System.Double":
                    return OdbcType.Double;
                default:
                    return OdbcType.Int;
            }
        }

        /// <summary>
        /// Method for finding the corresponding DB type to be used
        /// for the Hyper Cube type.
        /// </summary>
        private string GetDBType(XmlNode dbNode, string inputType)
        {
            string fetchType = String.Format(HyperCubeDBWriter.xVarTypeFetch, inputType);
            
            XmlNode typeNode = dbNode.SelectSingleNode(fetchType);
            if (null == typeNode)
                throw new ArgumentException("Unsupported DB type.");

            return ((string)typeNode.Attributes.GetNamedItem("DBType").Value);
        }

        /// <summary>
        /// This method creates the SP which would be used while inserting
        /// hyper cube data into the DB.
        /// </summary>
        /// <param name="dbNode"></param>
        private void CreateDataSP(XmlNode dbNode)
        {
            XmlNode tblNode = dbNode.SelectSingleNode(HyperCubeDBWriter.xCreateDataSP);
            string sqlString = (string)tblNode.Attributes.GetNamedItem("SQL").Value;

            // String members for holding the variable names
            StringBuilder spVarNames = new StringBuilder(HyperCubeDBWriter.xIndex + "[int]," 
                + HyperCubeDBWriter.yIndex + "[int]," + HyperCubeDBWriter.zIndex + "[int]," + 
                HyperCubeDBWriter.tIndex + "[int],");

            StringBuilder spValues = new StringBuilder(HyperCubeDBWriter.xIndex + ","
                + HyperCubeDBWriter.yIndex + "," + HyperCubeDBWriter.zIndex + "," +
                HyperCubeDBWriter.tIndex + ",");
            
            StringBuilder tableVarNames = new StringBuilder("xIndex, yIndex, zIndex, tIndex,");
            for (int i = 0; i < this.InputCube.Schema.ColumnCount; ++i)
            {
                spVarNames.Append("@" + this.InputCube.Schema.GetColumnName(i) + "Val [" + 
                    this.GetDBType(dbNode, this.InputCube.Schema.GetColumnType(i).ToString()) +
                    "] =NULL,");
                spValues.Append("@" + this.InputCube.Schema.GetColumnName(i) + "Val,");
                tableVarNames.Append(this.InputCube.Schema.GetColumnName(i) + ",");
            }
            // Remove the trailing commas
            spVarNames.Remove(spVarNames.Length - 1, 1);
            tableVarNames.Remove(tableVarNames.Length - 1, 1);
            spValues.Remove(spValues.Length - 1, 1);
            sqlString = String.Format(sqlString, spVarNames.ToString(),
                tableVarNames.ToString(), spValues.ToString());

            ExecuteCommand(sqlString);
        }

        /// <summary>
        /// This method creates the table to hold the data values for the
        /// columns of the HC. 
        /// </summary>
        private void CreateDataTable(XmlNode dbNode)
        {
            XmlNode tblNode = dbNode.SelectSingleNode(HyperCubeDBWriter.xCreateDataTable);

            string sqlString = (string)tblNode.Attributes.GetNamedItem("SQL").Value;

            // Get the sql string for specifying the column names.
            string colString = (string)tblNode.ChildNodes[0].Attributes.GetNamedItem("SQL").Value;
            StringBuilder queryStr = new StringBuilder(sqlString);

            for (int i = 0; i < this.InputCube.Schema.ColumnCount; ++i)
            {
                string strTemp = String.Format(colString, this.InputCube.Schema.GetColumnName(i),
                                    GetDBType(dbNode, this.InputCube.Schema.GetColumnType(i).ToString()));
                queryStr.Append(strTemp);
            }
            queryStr[queryStr.Length - 1] = ')';
            ExecuteCommand(queryStr.ToString());
        }

        /// <summary>
        /// This method reads the data values for the variables from the HyperCube
        /// and writes the same to the HyperCubeDataTable in the DB.
        /// can be changed to do a bulk insert for performance.
        /// </summary>
        /// <param name="dbNode"></param>
        private void InsertHyperCubeData(XmlNode dbNode)
        {
            XmlNode insNode = dbNode.SelectSingleNode(HyperCubeDBWriter.xInsDataValues);

            string sqlString = (string)insNode.Attributes.GetNamedItem("SQL").Value;
            StringBuilder colNames = new StringBuilder("");
            StringBuilder values = new StringBuilder("");

            int xLen = (null != this.InputCube.XDimensionValues) ? this.InputCube.XDimensionValues.Count : 1;
            int yLen = (null != this.InputCube.YDimensionValues) ? this.InputCube.YDimensionValues.Count : 1;
            int zLen = (null != this.InputCube.ZDimensionValues) ? this.InputCube.ZDimensionValues.Count : 1;
            int tLen = (null != this.InputCube.TDimensionValues) ? this.InputCube.TDimensionValues.Count : 1;

            // Create a new command object for executing the stored procedure
            // and add parameters for dimensions
            OdbcCommand spCmd = this.DbConnection.CreateCommand();
            spCmd.CommandType = System.Data.CommandType.StoredProcedure;

            OdbcParameter param = new OdbcParameter(HyperCubeDBWriter.xIndex, OdbcType.Int);
            spCmd.Parameters.Add(param);

            param = new OdbcParameter(HyperCubeDBWriter.yIndex, OdbcType.Int);
            spCmd.Parameters.Add(param);

            param = new OdbcParameter(HyperCubeDBWriter.zIndex, OdbcType.Int);
            spCmd.Parameters.Add(param);

            param = new OdbcParameter(HyperCubeDBWriter.tIndex, OdbcType.Int);
            spCmd.Parameters.Add(param);

            // Prepare the stored procedure command text depending on the
            // number of parameters to be passed to the SP.
            StringBuilder values1 = new StringBuilder();
            for (int i = 0; i < this.InputCube.Schema.ColumnCount; ++i)
            {
                values1.Append("?,");
                param = new OdbcParameter("@" + this.InputCube.Schema.GetColumnName(i) + "Val",
                                        this.GetODBCType(this.InputCube.Schema.GetColumnType(i)));
                spCmd.Parameters.Add(param);
            }
            values1.Remove(values1.Length - 1, 1);
            spCmd.CommandText = "{call InsertHyperCubeData(?,?,?,?," +values1.ToString() +")}";

            OdbcTransaction tr = this.DbConnection.BeginTransaction();
            spCmd.Transaction = tr;
            int colCount = this.InputCube.Schema.ColumnCount;
            for (int x = 0; x < xLen; x++)
            {
                for (int y = 0; y < yLen; y++)
                {
                    for (int z = 0; z < zLen; z++)
                    {
                        for (int t = 0; t < tLen; t++)
                        {
                            spCmd.Parameters[0].Value = x;
                            spCmd.Parameters[1].Value = y;
                            spCmd.Parameters[2].Value = z;
                            spCmd.Parameters[3].Value = t;
                            HyperCubeDataPoint dp = this.InputCube[x, y, z, t];

                            for (int i = 0, j =4; i < colCount; ++i,++j)
                            {
                                spCmd.Parameters[j].Value = dp[i];
                            }
                            try
                            {
                                spCmd.ExecuteNonQuery();
                            }
                            catch (Exception ex)
                            {
                                System.Console.WriteLine("Error occured while writing HyperCube Data");
                                System.Console.WriteLine(ex.Message);
                                tr.Rollback(); 
                                spCmd.Dispose();
                                return;
                            }
                        }
                    }
                }
            }
            tr.Commit();
        }

        /// <summary>
        /// Method for inserting dimension list data.
        /// </summary>
        private void InsertDimensionData(string strMain, string strSuffix, string endString,
            string tableName, List<object> values)
        {
            strMain = String.Format(strMain, tableName);
            StringBuilder queryStr = new StringBuilder(strMain);

            for (int i = 0; i < values.Count; ++i)
            {
                string strTemp;
                if(i == (values.Count-1))
                    strTemp = String.Format(endString, i.ToString(), values[i].ToString());
                else
                    strTemp = String.Format(strSuffix, i.ToString(), values[i].ToString());
                queryStr.Append(strTemp);
            }
            ExecuteCommand(queryStr.ToString());
        }

        /// <summary>
        /// Method for inserting the dimensionality values for the
        /// variables.
        /// </summary>
        /// <param name="dbNode"></param>
        private void InsertDimensionalityValues(XmlNode dbNode)
        {
            XmlNode insNode = dbNode.SelectSingleNode(HyperCubeDBWriter.xInsDimensionality);
            string sqlString = (string)insNode.Attributes.GetNamedItem("SQL").Value;

            for (int i = 0; i < this.InputCube.Schema.ColumnCount; ++i)
            {
                int dimensionalityValue = 0;
                dimensionalityValue |= (true == this.InputCube.Schema.HasDimension(i, HyperCube.Axis.X)) ?
                    1 : dimensionalityValue;
                dimensionalityValue |= (true == this.InputCube.Schema.HasDimension(i, HyperCube.Axis.Y)) ?
                    2 : dimensionalityValue;
                dimensionalityValue |= (true == this.InputCube.Schema.HasDimension(i, HyperCube.Axis.Z)) ?
                    4 : dimensionalityValue;
                dimensionalityValue |= (true == this.InputCube.Schema.HasDimension(i, HyperCube.Axis.T)) ?
                    8 : dimensionalityValue;

                string queryStr = string.Format(sqlString, this.InputCube.Schema.GetColumnName(i), dimensionalityValue);
                ExecuteCommand(queryStr);
            }
        }

        /// <summary>
        /// Method for inserting the values in the dimension lists to
        /// the DB tables.
        /// </summary>
        /// <param name="dbNode"></param>
        private void InsertDimensionValues(XmlNode dbNode)
        {
            XmlNode insNode = dbNode.SelectSingleNode(HyperCubeDBWriter.xInsDimValues);

            string sqlString = (string)insNode.Attributes.GetNamedItem("SQL").Value;
            string valString = (string)insNode.ChildNodes[0].Attributes.GetNamedItem("SQL").Value;
            string endString = (string)insNode.ChildNodes[1].Attributes.GetNamedItem("SQL").Value;

            if(null != this.InputCube.XDimensionValues)
                InsertDimensionData(sqlString, valString, endString, "XDimensionValues",
                    this.InputCube.XDimensionValues);

            if (null != this.InputCube.YDimensionValues)
                InsertDimensionData(sqlString, valString, endString, "YDimensionValues",
                    this.InputCube.YDimensionValues);

            if (null != this.InputCube.ZDimensionValues)
                InsertDimensionData(sqlString, valString, endString, "ZDimensionValues",
                    this.InputCube.ZDimensionValues);

            if (null != this.InputCube.TDimensionValues)
                InsertDimensionData(sqlString, valString, endString, "TDimensionValues",
                    this.InputCube.TDimensionValues);
        }

        /// <summary>
        /// This method creates the tables for storing the dimension values.
        /// </summary>
        private void CreateDimensionTables(XmlNode dbNode)
        {
            XmlNode tblNode = dbNode.SelectSingleNode(HyperCubeDBWriter.xCreateDimTable);

            string sqlString = (string)tblNode.Attributes.GetNamedItem("SQL").Value;

            // The dimension tables will be created for all the four dimensions irrespective
            // of whether the dimension has been mapped to a variable. Thus the type for
            // that dimension table would default to integer type.
            string typeString = "";
            if (string.IsNullOrEmpty(this.InputCube.Schema.XDimensionName))
                typeString = "int";
            else
                typeString = GetDBType(dbNode, this.InputCube.Schema.XDimensionType.ToString());
            
            string createTable = String.Format(sqlString, "XDimensionValues", typeString);
            ExecuteCommand(createTable);

            if (string.IsNullOrEmpty(this.InputCube.Schema.YDimensionName))
                typeString = "int";
            else
                typeString = GetDBType(dbNode, this.InputCube.Schema.YDimensionType.ToString());

            createTable = String.Format(sqlString, "YDimensionValues", typeString);
            ExecuteCommand(createTable);

            if (string.IsNullOrEmpty(this.InputCube.Schema.ZDimensionName))
                typeString = "int";
            else
                typeString = GetDBType(dbNode, this.InputCube.Schema.ZDimensionType.ToString());

            createTable = String.Format(sqlString, "ZDimensionValues", typeString);
            ExecuteCommand(createTable);

            if (string.IsNullOrEmpty(this.InputCube.Schema.TDimensionName))
                typeString = "int";
            else
                typeString = GetDBType(dbNode, this.InputCube.Schema.TDimensionType.ToString());

            createTable = String.Format(sqlString, "TDimensionValues", typeString);
            ExecuteCommand(createTable);
        }

        /// <summary>
        /// This method writes the attribute name values to the DB in the
        /// AttribNameValue table.
        /// </summary>
        private void InsertAttributeValues(XmlNode dbNode)
        {
            XmlNode dimNode = dbNode.SelectSingleNode(HyperCubeDBWriter.xInsAttribValues);

            string sqlString = (string)dimNode.Attributes.GetNamedItem("SQL").Value;
            string valString = (string)dimNode.ChildNodes[0].Attributes.GetNamedItem("SQL").Value;
            string endString = (string)dimNode.ChildNodes[1].Attributes.GetNamedItem("SQL").Value;

            StringBuilder values = new StringBuilder("");
            for (int i = 0; i < this.InputCube.Schema.ColumnCount; ++i)
            {
                values.Remove(0, values.Length);
                string colName = this.InputCube.Schema.GetColumnName(i);
                Dictionary<string, object> attribMap = this.InputCube.Schema.GetAllAttributes(i);
                Dictionary<string, object>.Enumerator attribEnum = attribMap.GetEnumerator();
                bool hasValue = attribEnum.MoveNext();
                do 
                {
                    string key = (true == hasValue) ? attribEnum.Current.Key : "";
                    object value = (true == hasValue) ? attribEnum.Current.Value: null;
                    bool nextVal = attribEnum.MoveNext();
                    if (hasValue && nextVal)
                        values.Append(String.Format(valString, colName, key,
                            GetAttributeValue(value)));
                    else if (hasValue)
                    {
                        values.Append(String.Format(endString, colName, key,
                            GetAttributeValue(value)));
                        hasValue = false;
                    }
                    else
                        hasValue = false;
                }while(hasValue);
                string queryString = sqlString + values.ToString();
                ExecuteCommand(queryString);
            }
        }

        /// <summary>
        /// This method tries to cast the object passed into one of the
        /// nc file array types to get the attribute value.
        /// </summary>
        /// <returns></returns>
        private string GetAttributeValue(object obj)
        {
            if (obj is byte[])
                return ((object[])obj)[0].ToString();
            else if (obj is Int16[])
                return ((Int16[])obj)[0].ToString();
            else if (obj is int[])
                return ((int[])obj)[0].ToString();
            else if (obj is float[])
                return ((float[])obj)[0].ToString();
            else if (obj is double[])
                return ((double[])obj)[0].ToString();
            else
                return ((object[])obj)[0].ToString();
        }

        
        /// <summary>
        /// Method to create the table to store the attribute values for
        /// the tables.
        /// </summary>
        private void CreateAttributeValuesTable(XmlNode dbNode)
        {
            XmlNode tblNode = dbNode.SelectSingleNode(xAttCreate);
            string sqlString = (string)tblNode.Attributes.GetNamedItem("SQL").Value;
            ExecuteCommand(sqlString);
        }

        /// <summary>
        /// Method to create the table to store the dimension values for
        /// the variables.
        /// </summary>
        private void CreateDimensionalityTable(XmlNode dbNode)
        {
            XmlNode tblNode = dbNode.SelectSingleNode(xDimensionMapCreate);
            string sqlString = (string)tblNode.Attributes.GetNamedItem("SQL").Value;
            ExecuteCommand(sqlString);
        }

        /// <summary>
        /// This method creates a new DB using the server name and DB name 
        /// parameters. It initialized the member connection and command
        /// parameters.
        /// </summary>
        /// <param name="dbNode"></param>
        private void CreatConnection(XmlNode dbNode)
        {
            XmlNode connNode = dbNode.SelectSingleNode(HyperCubeDBWriter.xConnNodeFetch);
            //string firstConnString = (string)connNode.Attributes.GetNamedItem("conn1").Value;
            //firstConnString = firstConnString.Replace("{0}", this.DBServer);

            //OdbcConnection DbConnectiontemp = new OdbcConnection(firstConnString);
            //try
            //{
            //    DbConnectiontemp.Open();
            //}
            //catch (Exception)
            //{
            //}

            //// Create the DB
            //XmlNode queryNode = dbNode.SelectSingleNode(HyperCubeDBWriter.xCreateDBFetch);
            //string createDB = (string)queryNode.Attributes.GetNamedItem("SQL").Value;
            //createDB = String.Format(createDB, this.DBName);

            //OdbcCommand cmd = DbConnectiontemp.CreateCommand();
            //cmd.CommandText = createDB;
            //try
            //{
            //    cmd.ExecuteNonQuery();
            //}
            //catch (Exception)
            //{
            //}
            //cmd.Dispose();
            //DbConnectiontemp.Close();
            //DbConnectiontemp.Dispose();

            string secondConnString = (string)connNode.Attributes.GetNamedItem("conn2").Value;
            secondConnString = secondConnString.Replace("{0}", this.DBServer);
            secondConnString = secondConnString.Replace("{1}", this.DBName);
            DbConnection = new OdbcConnection(secondConnString);

            try
            {
                DbConnection.Open();
                Command = DbConnection.CreateCommand();
            }
            catch (Exception)
            {
            }
        }

        /// <summary>
        /// This method reads the config xml and then loads the DB
        /// node as specified in the DBType input param
        /// </summary>
        /// <returns></returns>
        private XmlNode LoadXml()
        {
            XmlDocument dom = XmlUtilities.GetXmlResource(typeof(HyperCubeDBWriter).Assembly,
                        "ScientificWorkflow.Embedded.Xml.DBWriterConfig.xml");

            string xQuery = String.Format(HyperCubeDBWriter.xDBNodeFetch, this.DBType);
            XmlNode sqlNode = dom.SelectSingleNode(xQuery);
            if (null == sqlNode)
                throw new ArgumentException("HyperCubeDBWriter, Incorrect DB Type parameter.");
            
            return sqlNode;
        }

        #endregion
    }
}
